package ch.elca.lol.cake;

import ch.elca.lol.puissance4.board.Board;
import ch.elca.lol.puissance4.game.Token;
import ch.elca.lol.puissance4.game.Util;
import ch.elca.lol.puissance4.player.PlayerHelper;

// TODO: multi-thread, heuristic, alpha-beta
public class CakeAI extends PlayerHelper {
	// private CakeHeuristic heuristic = new CakeHeuristic();
	
	private final static boolean verbose = true;
	
	private final static int DEPTH = 4*2+1; // Should be odd
	
	private final static int UNPLAYABLE = 0;
	private final static int LOSS = -1;
	private final static int WIN = 1;
	private final static int NOTHING = 0;
	private final static int STALE = 0;
	
	@Override
	public int play(Board b) {
		turn++;
		
		if(turn <= 2) {
			return b.getWidth() / 2;
		}
		
		// Recursively look for the best outcome
		int[] scores = search(b, DEPTH, getToken());
		
		// TODO Manage unplayable columns better
		for(int x = 0; x < scores.length; x++) {
			if(!b.isPlayable(x))
				scores[x] = -9;
		}
		
		// Select best column from outcomes
		int col = Util.maxIndexRandom(scores);
		
		int val = scores[col];
		if(val == 1)
			say("CakeAI says: \"I'm going to win !\"");
		else if(val == -1)
			say("CakeAI says: \"I've got a bad feelibg.\"");
		
		// TODO: in case of equality, use the heuristic
		return col;
	}
	
	private void say(String msg) {
		if(verbose) 
			System.out.println(msg);
	}
	
	/**
	 * If depth is one, evaluate a single play. Otherwise recursively evaluate
	 * subplays and combine their outcomes to form a best guess.
	 */
	private int[] search(Board b, int depth, Token t) {
		if(depth == 1) {
			return singleMove(b, t);
		} else {
			// Examine each column, and explore it if it is playable
			int[] out = new int[b.getWidth()];
			for(int x = 0; x < b.getWidth(); x++) {
				out[x] = subPlay(b, x, depth, t);
			}
			return out;
		}
	}
	
	private int subPlay(Board b, int col, int depth, Token t) {
		if(!b.isPlayable(col))
			return UNPLAYABLE;
		
		// Play the move
		Board tmp = b.play(col, t);
		
		// Examine the score after this move, if its non zero, the game has
		// ended and the subplay stops here
		int localScore = gameScore(tmp);
		if(localScore != 0) {
			return localScore;
		}
		
		// Otherwise, recurse on subplays
		Token next = t.next();
		int[] sub = search(tmp, depth - 1, next);
		
		// Of all the outcomes, choose the best if we're playing our own turn,
		// or the worst if the adversary is playing
		boolean myTurn = (next == getToken());
		if(myTurn)
			return Util.max(sub);
		else
			return Util.min(sub);
	}
	
	private int gameScore(Board b) {
		if(b.hasWon(getToken()))
			return WIN;
		else if(b.hasWon(getToken().next()))
			return LOSS;
		else if(b.isGameOver())
			return STALE;
		else
			return NOTHING;
	}
	
	// Evaluates a single move
	private int[] singleMove(Board b, Token t) {
		int[] out = new int[b.getWidth()];
		Board tmp;
		
		for(int x = 0; x < b.getWidth(); x++) {
			if(!b.isPlayable(x))
				out[x] = UNPLAYABLE;
			else {
				tmp = b.play(x, t);
				out[x] = gameScore(tmp);
			}
		}
		
		return out;
	}
}
